#include "Tty.h"
#include "serial.h"
#include <stdlib.h>
#include <jimix/File.h>
#include <jimix/MonitorDriver.h>

Tty::Tty()
{
  resource = createResource((Callable*)this);
  addFunction(resource, (Resource::Call1)&Tty::tcgetattr, Tty_IF::tcgetattr);
  addFunction(resource, (Resource::Call1)&Tty::tcsetattr, Tty_IF::tcsetattr);
  addFunction(resource, (Resource::Call4)&Tty::fileRead,  FILE_READ);
  addFunction(resource, (Resource::Call4)&Tty::fileWrite, FILE_WRITE);
  
  properties.c_iflag = 0;
  properties.c_oflag = 0;
  properties.c_cflag = 0;
  properties.c_lflag = ECHO | ECHOE | ECHONL;
}

Tty::~Tty()
{
}

u32int Tty::fileRead(u32int impl, u32int offset, u32int size,
                     u8int *buffer)
{
  int tmplen = 0;
  while (tmplen < size)
  {
    char ch = read();
    if (ch == -1 && tmplen == 0)
    {
      // no characters available and none read.
      serial_handler(5);
      return 0xFFFFFFFF;
    }
    else if (ch == -1 && tmplen > 0)
    {
      // no characters available but some read. return now.
      return tmplen;
    }
    buffer[tmplen] = (u8int)ch;
    tmplen++;
  }
  return tmplen;
}

u32int Tty::fileWrite(u32int impl, u32int offset, u32int size,
                             u8int *buffer)
{
  bool b = getActive();
  if (b)
    setActive(false);
  for (int i = 0; i < size; i++)
  {
    write(buffer[i]);
  }
  if (b)
    setActive(true);
  return size;
}

void Tty::write(char *str)
{
  while(*str)
  {
    write(*str++);
  }
}

void Tty::write(char c)
{
}

char Tty::read()
{
  return (char)-1; /*EOF*/
}

void Tty::setActive(bool b)
{
}

LocalTty::LocalTty(u32int c, u32int r, char *desc)
{
  description = desc;
  C = c;
  R = r;
  monitor = requestResource((char*)"kerr");
  framebuffer = new u16int[C*R];

  u8int attributeByte = (0 << 4) | (15 & 0x0F);
  u16int blank = 0x20 /* space */ | (attributeByte << 8);
  for (int i = 0; i < C*R; i++)
  {
    framebuffer[i] = blank;
  }
  isActive = false;
  cursorX = cursorY = 0;
  bufStart = bufEnd = 0;
  writeTtyDescription();
}

LocalTty::~LocalTty()
{
}

void LocalTty::write(char *str)
{
  bool wasActive = isActive;
  isActive = false;
  while(*str)
  {
    write(*str++);
  }
  isActive = wasActive;
  if (wasActive)
  {
    refresh();
  }
}

void LocalTty::write(char c)
{
  // Colours would be set by a colour code. TODO: implement!
  u32int backColour = 0;
  u32int foreColour = 15;
  
  u8int  attributeByte = (backColour << 4) | (foreColour & 0x0F);
  u16int attribute = attributeByte << 8;
  u16int *location;
  
  // Handle a backspace, by moving the cursor back one space
  if (c == 0x08 && cursorX)
  {
    backspace();
    return;
  }

  // Handle a tab by increasing the cursor's X, but only to a point
  // where it is divisible by 8.
  else if (c == 0x09)
  {
    cursorX = (cursorX+8) & ~(8-1);
  }
  
  // Handle carriage return
  else if (c == '\r')
  {
    cursorX = 0;
  }

  // Handle newline by moving cursor back to left and increasing the row
  else if (c == '\n')
  {
    cursorX = 0;
    cursorY++;
  }
  
  else if(c >= ' ')
  {
    location = (u16int*)framebuffer + (cursorY*C + cursorX);
    *location = c | attribute;
    cursorX++;
  }

  // Check if we need to insert a new line because we have reached the end 
  // of the screen.
  if (cursorX >= C)
  {
    cursorX = 0;
    cursorY ++;
  }
  
  // Scroll the screen if needed
  scroll();
  
  if (isActive)
  {
    refresh();
  }
}

char LocalTty::read()
{
  if (bufEnd <= bufStart) // No data in buffer?
  {
    return -1; // Return.
  }
  int tmp = bufStart%256;
  bufStart ++;
  return buffer[tmp];
}

void LocalTty::setActive(bool b)
{
  isActive = b;
  if (b)
  {
    refresh();
  }
}

void LocalTty::refresh()
{
  callResource(monitor, MonitorDriver_IF::writeRaw, framebuffer, cursorX, cursorY);
}

void LocalTty::scroll()
{
  // Get a space character with the default colour attributes.
  u8int attributeByte = (0 << 4) | (15 & 0x0F);
  u16int blank = 0x20 /* space */ | (attributeByte << 8);

  // Row 25 is the end, this means we need to scroll up
  if(cursorY >= R)
  {
      // Move the current text chunk that makes up the screen
      // back in the buffer by a line 
      
    for (int i = 0; i < (R-1)*C; i++)
    {
      framebuffer[i] = framebuffer[i+C];
    }

    for (int i = (R-1)*C; i < R*C; i++)
    {
      framebuffer[i] = blank;
    }
    cursorY = R-1;
  }
  
  // Write the TTY name in the upper right corner.
  writeTtyDescription();
}

void LocalTty::inputReceived(char c)
{
  if (bufEnd+1 != bufStart) // Buffer not full
  {
    buffer[bufEnd%256] = c;
    bufEnd ++;
  }
}

void LocalTty::ungetc(char c)
{
  bufStart --;
  buffer[bufStart%256] = c;
}

void LocalTty::backspace()
{
  // Colours would be set by a colour code. TODO: implement!
  u32int backColour = 0;
  u32int foreColour = 15;
  
  u8int  attributeByte = (backColour << 4) | (foreColour & 0x0F);
  u16int attribute = attributeByte << 8;
  u16int *location;
  
  // Handle a backspace, by moving the cursor back one space
  if (cursorX)
  {
    cursorX--;
  }
  
  location = (u16int*)framebuffer + (cursorY*C + cursorX);
  *location = ' ' | attribute;
  
  if (isActive)
  {
    refresh();
  }
}

void LocalTty::writeTtyDescription()
{
  // Length of string to write is strlen(description)+2 for spaces either end.
  int len = strlen(description)+2;
  
  for (int i = 80-len, j=0; i < 80; i++,j++)
  {
    u32int backColour = 1;
    u32int foreColour = 15;
  
    u8int  attributeByte = (backColour << 4) | (foreColour & 0x0F);
    u16int attribute = attributeByte << 8;
    u16int *location = (u16int*)framebuffer + (0*C/*top row*/ + i);
    char c;
    if (j == 0 || j == len-1)
    {
      c = ' ';
    }
    else
    {
      c = description[j-1];
    }
    *location = c | attribute;
  }
}

SerialTty::SerialTty(char *desc, u16int p)
{
  description = desc;
  port = p;
  bufStart = bufEnd = 0;
}

char SerialTty::read()
{
  if (bufEnd <= bufStart) // No data in buffer?
  {
    return -1; // Return.
  }
  int tmp = bufStart%256;
  bufStart ++;
  return buffer[tmp];
}

void SerialTty::write(char c)
{
  // If we are outputting a newline, we need a carriage-return as well.
  if (c == '\n')
  {
    serial_put('\r', port);
  }
  serial_put(c, port);
}

void SerialTty::inputReceived(char c)
{
  if (bufEnd+1 != bufStart) // Buffer not full
  {
    buffer[bufEnd%256] = c;
    bufEnd ++;
  }
}

void SerialTty::ungetc(char c)
{
  bufStart --;
  buffer[bufStart%256] = c;
}
